package com.zhg.shortlink.service;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.zhg.shortlink.config.ShortCodeConfig;
import com.zhg.shortlink.constant.CommonConstants;
import com.zhg.shortlink.domain.dto.CreateShortUrlRequest;
import com.zhg.shortlink.domain.dto.CreateShortUrlResponse;
import com.zhg.shortlink.domain.entity.ShortUrlMapping;
import com.zhg.shortlink.mapper.ShortUrlMappingMapper;
import com.zhg.shortlink.util.DigestUtils;
import io.micrometer.common.util.StringUtils;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import java.time.LocalDateTime;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;

/**
 * @author 朱洪刚
 * @version 1.0
 * @data 2025/10/20 13:39
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ShortUrlService {
    private final ShortUrlMappingMapper shortUrlRepository;
    private final ShortCodeService shortCodeService;

    @Value("${shortlink.shorturl.domain:http://localhost:8001}")
    private String domain;

    @Value("${shortlink.shorturl.default-expire-days:0}")
    private Integer defaultExpireDays;

    private final TransactionTemplate transactionTemplate;

    // 添加分布式锁服务依赖
    private final DistributedLockService distributedLockService;

    private final ShortCodeConfig shortCodeConfig;

    private final ClusterAwareCacheService clusterAwareCacheService;
    private final ShardingStrategyService shardingStrategyService;

    /**
     * 创建短链（支持分库分表）
     *
     * @param request
     * @return
     */
    @SentinelResource(
            value = "createShortUrl",
            blockHandler = "createShortUrlBlockHandler",
            fallback = "createShortUrlFallback"
    )
    public CreateShortUrlResponse createShortUrl(CreateShortUrlRequest request) {
        // 参数校验
        if (request == null || StringUtils.isBlank(request.getOriginUrl())) {
            throw new IllegalArgumentException("长链接不能为空");
        }

        // 计算原始URL的哈希值
        String originUrlHash = calculateUrlHash(request.getOriginUrl());

        // 第一层防护：先从缓存查询是否存在URL哈希映射
        String cachedShortCode = clusterAwareCacheService.getShortCodeByUrlHash(originUrlHash);
        if (StringUtils.isNotBlank(cachedShortCode)) {
            // 验证缓存的短链是否有效
            CreateShortUrlResponse cachedResponse = getShortUrlInfo(cachedShortCode);
            if (cachedResponse != null) {
                log.info("集群缓存命中，返回已存在的短链: shortCode={}, originUrl={}, 分片槽位={}",
                        cachedShortCode, request.getOriginUrl(),
                        shardingStrategyService.calculateSlot(cachedShortCode));
                return cachedResponse;
            } else {
                // 缓存的短链无效，清除映射缓存
                clusterAwareCacheService.removeUrlHashMapping(originUrlHash);
            }
        }

        // 第二层防护：使用分布式锁保证同一URL的串行处理
        String lockKey = "create_url:" + originUrlHash;
        return distributedLockService.executeWithLock(lockKey, () -> {
            // 在锁内再次检查缓存（双重检查）
            String doubleCheckShortCode = clusterAwareCacheService.getShortCodeByUrlHash(originUrlHash);
            if (StringUtils.isNotBlank(doubleCheckShortCode)) {
                CreateShortUrlResponse doubleCheckResponse = getShortUrlInfo(doubleCheckShortCode);
                if (doubleCheckResponse != null) {
                    log.info("双重检查集群缓存命中: shortCode={}, originUrl={}, 分片槽位={}",
                            doubleCheckShortCode, request.getOriginUrl(),
                            shardingStrategyService.calculateSlot(doubleCheckShortCode));
                    return doubleCheckResponse;
                }
            }

            // 生成短链编码
            final String initialShortCode = shortCodeService.generateByStrategy(request.getOriginUrl());
            try {
                // // 第三层防护：数据库事务 + 唯一索引
                return transactionTemplate.execute(status -> {
                    try {
                        String currentShortCode = initialShortCode;
                        // 检查生成的短链code是否已存在（避免哈希冲突）
                        LambdaQueryWrapper<ShortUrlMapping> queryWrapper = new LambdaQueryWrapper<>();
                        queryWrapper.eq(ShortUrlMapping::getShortCode, currentShortCode);
                        ShortUrlMapping shortUrlMapping = shortUrlRepository.selectOne(queryWrapper);
                        if (!Objects.isNull(shortUrlMapping)) {
                            // 如果是相同的原始URL，直接返回
                            if (originUrlHash.equals(shortUrlMapping.getOriginUrlHash())) {
                                clusterAwareCacheService.putToCache(currentShortCode, shortUrlMapping);
                                clusterAwareCacheService.putUrlHashMapping(originUrlHash, currentShortCode);
                                log.info("发现相同URL的短链: shortCode={}, originUrl={}, 数据库分片: db={}, table={}, Redis分片槽位={}",
                                        currentShortCode, request.getOriginUrl(),
                                        calculateDatabaseIndex(currentShortCode), calculateTableIndex(currentShortCode),
                                        shardingStrategyService.calculateSlot(currentShortCode));
                                return buildResponse(shortUrlMapping);
                            } else {
                                // 哈希冲突，重新生成
                                int retryCount = 0;
                                int maxRetries = shortCodeConfig.getMaxRetries();
                                while (shortUrlRepository.exists(queryWrapper) && retryCount < maxRetries) {
                                    currentShortCode = shortCodeService.generateByStrategy(request.getOriginUrl() + retryCount);
                                    queryWrapper.clear();
                                    queryWrapper.eq(ShortUrlMapping::getShortCode, currentShortCode);
                                    retryCount++;
                                }
                                if (retryCount >= maxRetries) {
                                    throw new RuntimeException("生成短链失败，请重试");
                                }
                            }
                        }
                        // 创建短链实体
                        shortUrlMapping = new ShortUrlMapping();
                        shortUrlMapping.setShortCode(currentShortCode);
                        shortUrlMapping.setOriginUrl(request.getOriginUrl());
                        shortUrlMapping.setOriginUrlHash(originUrlHash);
                        shortUrlMapping.setExpireDays(request.getExpireDays() != null ? request.getExpireDays() : defaultExpireDays);
                        shortUrlMapping.setCreator(request.getCreator());
                        shortUrlMapping.setCreateTime(LocalDateTime.now());
                        shortUrlMapping.setStatus(1); // 默认状态为正常

                        // 在保存到数据库之前添加日志
                        log.info("准备保存短链: shortCode={}, originUrl={}, originUrlHash={}, 数据库分片: db={}, table={}, Redis分片槽位={}",
                                currentShortCode, request.getOriginUrl(), originUrlHash,
                                calculateDatabaseIndex(currentShortCode), calculateTableIndex(currentShortCode),
                                shardingStrategyService.calculateSlot(currentShortCode));

                        // 保存到数据库
                        shortUrlRepository.insert(shortUrlMapping);

                        log.info("短链保存完成: shortCode={}, id={}", currentShortCode, shortUrlMapping.getShortCode());


                        // 添加到布隆过滤器
                        clusterAwareCacheService.addToBloomFilter(currentShortCode);

                        // 缓存短链信息和URL哈希映射到Redis集群
                        clusterAwareCacheService.putToCache(currentShortCode, shortUrlMapping);
                        clusterAwareCacheService.putUrlHashMapping(originUrlHash, currentShortCode);

                        log.info("创建短链成功: shortCode={}, originUrl={}, Redis分片槽位={}",
                                currentShortCode, request.getOriginUrl(),
                                shardingStrategyService.calculateSlot(currentShortCode));

                        return buildResponse(shortUrlMapping);
                    } catch (Exception e) {
                        // 手动标记事务回滚
                        status.setRollbackOnly();
                        throw e;
                    }
                });

            } catch (Exception e) {
                log.error("创建短链失败: originUrl={}, error={}", request.getOriginUrl(), e.getMessage(), e);
                throw new RuntimeException("创建短链失败: " + e.getMessage(), e);
            }
        });
    }


    /**
     * 获取短链信息（支持分库分表）
     */
    @SentinelResource(
            value = "getShortUrlInfo",
            blockHandler = "getShortUrlInfoBlockHandler",
            fallback = "getShortUrlInfoFallback"
    )
    public CreateShortUrlResponse getShortUrlInfo(String shortCode) {
        // 参数校验
        if (StringUtils.isBlank(shortCode)) {
            return null;
        }

        // 先检查布隆过滤器
        if (!clusterAwareCacheService.existsInBloomFilter(shortCode)) {
            log.debug("布隆过滤器检查失败: shortCode={}", shortCode);
            return null;
        }

        //获取短链映射（会自动路由到正确分片）
        ShortUrlMapping shortUrlMapping = getShortUrlWithSentinel(shortCode);
        if (Objects.isNull(shortUrlMapping)) {
            log.debug("短链不存在: shortCode={}", shortCode);
            return null;
        }
        // 检查是否过期
        if (shortUrlMapping.isExpired()) {
            log.debug("短链已过期: shortCode={}, createTime={}, expireDays={}",
                    shortCode, shortUrlMapping.getCreateTime(), shortUrlMapping.getExpireDays());
            return null;
        }

        // 检查状态
        if (shortUrlMapping.getStatus() == null || shortUrlMapping.getStatus() != 1) {
            log.debug("短链状态异常: shortCode={}, status={}", shortCode, shortUrlMapping.getStatus());
            return null;
        }
        return buildResponse(shortUrlMapping);
    }

    /**
     * 计算数据库索引（更新为32个数据库）
     */
    private int calculateDatabaseIndex(String shortCode) {

        return Math.abs(shortCode.hashCode()) % CommonConstants.SHARDING_DATABASE_COUNT;

    }

    /**
     * 计算表索引（更新为256张表）
     */
    private int calculateTableIndex(String shortCode) {

        return Math.abs(shortCode.hashCode()) % CommonConstants.SHARDING_TABLE_COUNT;

    }

    /**
     * 获取原始URL（用于重定向，支持分库分表）
     */
    @SentinelResource(
            value = "getOriginUrl",
            blockHandler = "getOriginUrlBlockHandler",
            fallback = "getOriginUrlFallback"
    )
    public String getOriginUrl(String shortCode) {
        CreateShortUrlResponse shortUrlInfo = getShortUrlInfo(shortCode);
        if (Objects.isNull(shortUrlInfo)) {
            recordNotExistLog(shortCode);
            return null;
        }

        // 异步更新访问次数（不阻塞主流程）
        updateAccessCountAsync(shortCode);

        log.debug("短链重定向: shortCode={}, originUrl={}, 路由到分片: db={}, table={}",
                shortCode, shortUrlInfo.getOriginUrl(),
                calculateDatabaseIndex(shortCode), calculateTableIndex(shortCode));

        return shortUrlInfo.getOriginUrl();
    }

    /**
     * 异步更新访问次数（支持分库分表）
     */
    @SentinelResource(value = "updateAccessCount")
    public void updateAccessCountAsync(String shortCode) {
        try {
            // 先尝试从缓存增加计数
            Long count = clusterAwareCacheService.incrementAccessCount(shortCode);

            // 异步更新数据库（可以考虑批量更新）
            if (count != null && count % 100 == 0) {
                // 每100次访问同步一次数据库（ShardingSphere会自动路由）
                updateAccessCountInDatabase(shortCode, count);
            }
        } catch (Exception e) {
            // 访问计数失败不影响主流程
            log.warn("更新访问次数失败: shortCode={}, error={}", shortCode, e.getMessage());
        }
    }

    /**
     * 数据库访问次数更新（支持分库分表）
     */
    @Transactional(rollbackFor = Exception.class)  // 添加明确的回滚策略
    public void updateAccessCountInDatabase(String shortCode, Long accessCount) {
        try {
            LambdaUpdateWrapper<ShortUrlMapping> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(ShortUrlMapping::getShortCode, shortCode);
            updateWrapper.set(ShortUrlMapping::getAccessCount, accessCount);
            int updated = shortUrlRepository.update(updateWrapper);
            if (updated > 0) {
                log.debug("访问次数更新成功: shortCode={}, accessCount={}, 路由到分片: db={}, table={}",
                        shortCode, accessCount,
                        calculateDatabaseIndex(shortCode), calculateTableIndex(shortCode));
            } else {
                log.warn("访问次数更新失败，记录不存在: shortCode={}", shortCode);
            }
        } catch (Exception e) {
            log.error("数据库访问次数更新失败: shortCode={}, accessCount={}, error={}",
                    shortCode, accessCount, e.getMessage(), e);
            throw e;  // 重新抛出异常，让事务回滚
        }
    }

    private ShortUrlMapping getShortUrlWithSentinel(String shortCode) {
        // 从缓存获取
        ShortUrlMapping shortUrlMapping = clusterAwareCacheService.getFromCache(shortCode);
        if (shortUrlMapping != null) {
            return shortUrlMapping;
        }

        // 缓存未命中，查询数据库（ShardingSphere会自动路由到正确的分片）
        try {
            LambdaQueryWrapper<ShortUrlMapping> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(ShortUrlMapping::getShortCode, shortCode);
            shortUrlMapping = shortUrlRepository.selectOne(queryWrapper);
            if (Objects.nonNull(shortUrlMapping)) {
                // 缓存查询结果
                clusterAwareCacheService.putToCache(shortCode, shortUrlMapping);
                log.debug("数据库查询成功: shortCode={}, 路由到分片: db={}, table={}",
                        shortCode, calculateDatabaseIndex(shortCode), calculateTableIndex(shortCode));
                return shortUrlMapping;
            }
        } catch (Exception e) {
            log.error("数据库查询失败: shortCode={}, error={}", shortCode, e.getMessage(), e);
            throw e;
        }
        recordNotExistLog(shortCode);
        return null;
    }

    private void recordNotExistLog(String shortCode) {
        log.debug("短链不存在: shortCode={}", shortCode);
    }

    private String calculateUrlHash(String originUrl) {
        // 计算URL的哈希值，这里使用简单的MD5算法
        return DigestUtils.md5(originUrl);
    }

    /**
     * 构建响应对象
     */
    private CreateShortUrlResponse buildResponse(ShortUrlMapping shortUrlMapping) {
        CreateShortUrlResponse response = new CreateShortUrlResponse();
        response.setShortCode(shortUrlMapping.getShortCode());
        response.setShortUrl(domain + "/shortUrl/" + shortUrlMapping.getShortCode());
        response.setOriginUrl(shortUrlMapping.getOriginUrl());
        response.setCreateTime(shortUrlMapping.getCreateTime());
        response.setExpireDays(shortUrlMapping.getExpireDays());
        response.setAccessCount(shortUrlMapping.getAccessCount());
        return response;
    }

    // ==================== Sentinel 处理方法 ====================

    public CreateShortUrlResponse createShortUrlBlockHandler(CreateShortUrlRequest request, BlockException ex) {
        log.warn("创建短链被限流: originUrl={}", request != null ? request.getOriginUrl() : "null");
        CreateShortUrlResponse response = new CreateShortUrlResponse();
        response.setShortCode("RATE_LIMITED");
        response.setShortUrl("系统繁忙，请稍后重试");
        return response;
    }

    public CreateShortUrlResponse createShortUrlFallback(CreateShortUrlRequest request, Throwable ex) {
        log.error("创建短链降级: originUrl={}, error={}",
                request != null ? request.getOriginUrl() : "null", ex.getMessage());
        CreateShortUrlResponse response = new CreateShortUrlResponse();
        response.setShortCode("SERVICE_DEGRADED");
        response.setShortUrl("创建短链失败，请稍后重试");
        return response;
    }

    public CreateShortUrlResponse getShortUrlInfoBlockHandler(String shortCode, BlockException ex) {
        log.warn("查询短链信息被限流: shortCode={}", shortCode);
        return null;
    }

    public CreateShortUrlResponse getShortUrlInfoFallback(String shortCode, Throwable ex) {
        log.error("查询短链信息降级: shortCode={}, error={}", shortCode, ex.getMessage());
        return null;
    }

    public String getOriginUrlBlockHandler(String shortCode, BlockException ex) {
        log.warn("获取原始URL被限流: shortCode={}", shortCode);
        return null;
    }

    public String getOriginUrlFallback(String shortCode, Throwable ex) {
        log.error("获取原始URL降级: shortCode={}, error={}", shortCode, ex.getMessage());
        return null;
    }

    public ShortUrlMapping databaseQueryBlockHandler(String shortCode, BlockException ex) {
        log.warn("数据库查询被限流: shortCode={}", shortCode);
        return null;
    }

    public ShortUrlMapping databaseQueryFallback(String shortCode, Throwable ex) {
        log.error("数据库查询降级: shortCode={}, error={}", shortCode, ex.getMessage());
        return null;
    }


}
